package de.tudarmstadt.fgbs.cafe.core;

import java.util.Arrays;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import de.tudarmstadt.fgbs.cafe.core.configuration.ConfigUtility;
import de.tudarmstadt.fgbs.cafe.data.DataRetrieverInterface;
import de.tudarmstadt.fgbs.cafe.data.SystemDataManagerInterface;
import de.tudarmstadt.fgbs.cafe.exception.FlawedConfigurationException;
import de.tudarmstadt.fgbs.cafe.filesystem.AutoSaveInterface;
import de.tudarmstadt.fgbs.cafe.importexport.ExportCoreInterface;
import de.tudarmstadt.fgbs.cafe.importexport.ExportInterface;
import de.tudarmstadt.fgbs.cafe.importexport.ImportCoreInterface;
import de.tudarmstadt.fgbs.cafe.importexport.ImportInterface;
import de.tudarmstadt.fgbs.cafe.login.LoginInterface;
import de.tudarmstadt.fgbs.cafe.structure.enumeration.Subsystem.Subystem;

public class SubsystemFactory {

    /**
     * The log which is used to log all behaviors of this class.
     */
    private static Log LOG = LogFactory.getLog(SubsystemFactory.class);

    /**
     * The file name of the configuration file which contains the
     * fully-qualified class names of the interface's implementing classes.
     */
    private static final String CONFIG_FILE_NAME = "subsystemFactory";

    /**
     * Key in the configuration file, the fully-qualified class name of the
     * login subsystem is associated to.
     */
    private static final String CONFIG_KEY_LOGIN_SUBSYSTEM = "loginSubsystem";

    /**
     * Key in the configuration file, the fully-qualified class name of the
     * import subsystem is associated to.
     */
    private static final String CONFIG_KEY_IMPORT_SUBSYSTEM = "importSubsystem";

    /**
     * Key in the configuration file, the fully-qualified class name of the
     * export subsystem is associated to.
     */
    private static final String CONFIG_KEY_EXPORT_SUBSYSTEM = "exportSubsystem";

    /**
     * Key in the configuration file, the fully-qualified class name of the data
     * subsystem is associated to.
     */
    private static final String CONFIG_KEY_DATA_SUBSYSTEM = "dataSubsystem";

    /**
     * Key in the configuration file, the fully-qualified class name of the
     * system data storage subsystem is associated to.
     */
    private static final String CONFIG_KEY_SYSTEM_DATA_SUBSYSTEM = "systemDataMangerSubsystem";

    private static final String CONFIG_KEY_SYSTEM_AUTOSAVE_SUBSYSTEM = "autoSaveSubsystem";

    /**
     * The only instance of this class.
     */
    private static SubsystemFactory instance;

    /**
     * The instance of a class implementing the {@link LoginInterface}.
     */
    private LoginInterface loginInstance;

    private AutoSaveInterface autosaveInstance;

    /**
     * The instance of a class implementing the {@link ImportInterface}.
     */
    private ImportInterface importInterface;

    /**
     * The instance of a class implementing the {@link ExportInterface}.
     */
    private ExportInterface exportInterface;

    /**
     * The instance of a class implementing the {@link DataRetrieverInterface}.
     */
    private DataRetrieverInterface dataRetrieverInterface;

    /**
     * The instance of a class implementing the
     * {@link SystemDataManagerInterface}.
     */
    private SystemDataManagerInterface systemDataManagerInterface;

    /**
     * Constructor which is used to prevent instantiation of this singleton from
     * other classes.
     */
    private SubsystemFactory() {
	super();
	if (LOG.isTraceEnabled())
	    LOG.trace("# new instance of SubsystemFactory is created");
    }

    /**
     * Gets an instance of a class implementing the
     * {@link SystemDataManagerInterface}.<br>
     * The implementing class is configured in the configuration file with the
     * key {@link #CONFIG_KEY_SYSTEM_DATA_SUBSYSTEM}.
     * 
     * @return An instance of a class implementing the
     *         {@link SystemDataManagerInterface}.
     * @throws FlawedConfigurationException
     *             If the configuration is faulty and no instance of a class
     *             implementing the {@link SystemDataManagerInterface} can be
     *             created.
     */
    public SystemDataManagerInterface getSystemDataMangerSubsystem()
	    throws FlawedConfigurationException {
	if (LOG.isTraceEnabled())
	    LOG.trace("# SubsystemFactory.getSystemDataMangerSubsystem()");
	if (systemDataManagerInterface == null) {
	    if (LOG.isDebugEnabled())
		LOG.debug("# new instance has to be created");
	    // read fully-qualified class name of implementing class from
	    // ConfigUtility
	    String fullClassName = ConfigUtility.loadProperty(CONFIG_FILE_NAME,
		    CONFIG_KEY_SYSTEM_DATA_SUBSYSTEM);
	    if (fullClassName != null) {
		// create new instance with constructor without parameters
		systemDataManagerInterface = (SystemDataManagerInterface) createInstance(
			fullClassName, null, null);
	    }
	    if (systemDataManagerInterface == null) {
		if (LOG.isWarnEnabled())
		    LOG.warn("# system data storage subsystem could not be instantiated");
		throw new FlawedConfigurationException(
			"Config file ["
				+ CONFIG_FILE_NAME
				+ "] or its value of key["
				+ CONFIG_KEY_SYSTEM_DATA_SUBSYSTEM
				+ "] is faulty. -> System data storage subsystem could not be instantiated.",
			Subystem.SYSTEM_DATA);
	    }
	}
	return systemDataManagerInterface;
    }

    /**
     * Gets an instance of a class implementing the {@link LoginInterface}.<br>
     * The implementing class is configured in the configuration file with the
     * key {@link #CONFIG_KEY_LOGIN_SUBSYSTEM}.
     * 
     * @return An instance of a class implementing the {@link LoginInterface}.
     * @throws FlawedConfigurationException
     *             If the configuration is faulty and no instance of a class
     *             implementing the {@link LoginInterface} can be created.
     */
    public LoginInterface getLoginSubsystem()
	    throws FlawedConfigurationException {
	if (LOG.isTraceEnabled())
	    LOG.trace("# SubsystemFactory.getLoginSubsystem()");
	if (loginInstance == null) {
	    if (LOG.isDebugEnabled())
		LOG.debug("# new instance has to be created");
	    // read fully-qualified class name of implementing class from
	    // ConfigUtility
	    String fullClassName = ConfigUtility.loadProperty(CONFIG_FILE_NAME,
		    CONFIG_KEY_LOGIN_SUBSYSTEM);
	    if (fullClassName != null) {
		// create new instance with constructor without parameters
		loginInstance = (LoginInterface) createInstance(fullClassName,
			null, null);
	    }
	    if (loginInstance == null) {
		if (LOG.isWarnEnabled())
		    LOG.warn("# login subsystem could not be instantiated");
		throw new FlawedConfigurationException(
			"Config file ["
				+ CONFIG_FILE_NAME
				+ "] or its value of key["
				+ CONFIG_KEY_LOGIN_SUBSYSTEM
				+ "] is faulty. -> Login subsystem could not be instantiated.",
			Subystem.LOGIN);
	    }
	}
	return loginInstance;
    }

    /**
     * Gets an instance of a class implementing the {@link ImportInterface}.<br>
     * The implementing class is configured in the configuration file with the
     * key {@link #CONFIG_KEY_IMPORT_SUBSYSTEM}.
     * 
     * @param core
     *            The Core to direct system calls to.
     * @return An instance of a class implementing the {@link ImportInterface}.
     * @throws FlawedConfigurationException
     *             If the configuration is faulty and no instance of a class
     *             implementing the {@link ImportInterface} can be created.
     */
    public ImportInterface getImportSubsystem(ImportCoreInterface core)
	    throws FlawedConfigurationException {
	if (LOG.isTraceEnabled())
	    LOG.trace("# SubsystemFactory.getImportSubsystem(" + core + ")");
	if (importInterface == null) {
	    if (LOG.isDebugEnabled())
		LOG.debug("# new instance has to be created");
	    // read fully-qualified class name of implementing class from
	    // ConfigUtility
	    String fullClassName = ConfigUtility.loadProperty(CONFIG_FILE_NAME,
		    CONFIG_KEY_IMPORT_SUBSYSTEM);
	    if (fullClassName != null) {
		// set parameters of the constructor
		Class<?>[] classParams = { ImportCoreInterface.class };
		Object[] objectParams = { core };
		// create new instance
		importInterface = (ImportInterface) createInstance(
			fullClassName, classParams, objectParams);
	    }
	    if (importInterface == null) {
		if (LOG.isWarnEnabled())
		    LOG.warn("# import subsystem could not be instantiated");
		throw new FlawedConfigurationException(
			"Config file ["
				+ CONFIG_FILE_NAME
				+ "] or its value of key["
				+ CONFIG_KEY_IMPORT_SUBSYSTEM
				+ "] is faulty. -> Import subsystem could not be instantiated.",
			Subystem.IMPORT);
	    }
	}
	return importInterface;
    }

    /**
     * Gets an instance of a class implementing the {@link ExportInterface}.<br>
     * The implementing class is configured in the configuration file with the
     * key {@link #CONFIG_KEY_EXPORT_SUBSYSTEM}.
     * 
     * @param core
     *            The Core to direct system calls to.
     * @return An instance of a class implementing the {@link ExportInterface}.
     * @throws FlawedConfigurationException
     *             If the configuration is faulty and no instance of a class
     *             implementing the {@link ExportInterface} can be created.
     */
    public ExportInterface getExportSubsystem(ExportCoreInterface core)
	    throws FlawedConfigurationException {
	if (LOG.isTraceEnabled())
	    LOG.trace("# SubsystemFactory.getExportSubsystem(" + core + ")");
	if (exportInterface == null) {
	    if (LOG.isDebugEnabled())
		LOG.debug("# new instance has to be created");
	    // read fully-qualified class name of implementing class from
	    // ConfigUtility
	    String fullClassName = ConfigUtility.loadProperty(CONFIG_FILE_NAME,
		    CONFIG_KEY_EXPORT_SUBSYSTEM);
	    if (fullClassName != null) {
		// set parameters of the constructor
		Class<?>[] classParams = { ExportCoreInterface.class };
		Object[] objectParams = { core };
		// create new instance
		exportInterface = (ExportInterface) createInstance(
			fullClassName, classParams, objectParams);
	    }
	    if (exportInterface == null) {
		if (LOG.isWarnEnabled())
		    LOG.warn("# export subsystem could not be instantiated");
		throw new FlawedConfigurationException(
			"Config file ["
				+ CONFIG_FILE_NAME
				+ "] or its value of key["
				+ CONFIG_KEY_EXPORT_SUBSYSTEM
				+ "] is faulty. -> Export subsystem could not be instantiated.",
			Subystem.EXPORT);
	    }
	}
	return exportInterface;
    }

    /**
     * Gets an instance of a class implementing the
     * {@link DataRetrieverInterface}.<br>
     * The implementing class is configured in the configuration file with the
     * key {@link #CONFIG_KEY_DATA_SUBSYSTEM}.
     * 
     * @return An instance of a class implementing the
     *         {@link DataRetrieverInterface}.
     * @throws FlawedConfigurationException
     *             If the configuration is faulty and no instance of a class
     *             implementing the {@link DataRetrieverInterface} can be
     *             created.
     */
    public DataRetrieverInterface getDataRetrieverSubsystem()
	    throws FlawedConfigurationException {
	if (LOG.isTraceEnabled())
	    LOG.trace("# SubsystemFactory.getDataRetrieverSubsystem()");
	if (dataRetrieverInterface == null) {
	    if (LOG.isDebugEnabled())
		LOG.debug("# new instance has to be created");
	    // read fully-qualified class name of implementing class from
	    // ConfigUtility
	    String fullClassName = ConfigUtility.loadProperty(CONFIG_FILE_NAME,
		    CONFIG_KEY_DATA_SUBSYSTEM);
	    if (fullClassName != null) {
		// create new instance with constructor without parameters
		dataRetrieverInterface = (DataRetrieverInterface) createInstance(
			fullClassName, null, null);
	    }
	    if (dataRetrieverInterface == null) {
		if (LOG.isWarnEnabled())
		    LOG.warn("# data subsystem could not be instantiated");
		throw new FlawedConfigurationException(
			"Config file ["
				+ CONFIG_FILE_NAME
				+ "] or its value of key["
				+ CONFIG_KEY_DATA_SUBSYSTEM
				+ "] is faulty. -> Data subsystem could not be instantiated.",
			Subystem.IMPORT);
	    }
	}
	return dataRetrieverInterface;
    }

    public AutoSaveInterface getAutoSaveSubsystem()
	    throws FlawedConfigurationException {
	if (LOG.isTraceEnabled())
	    LOG.trace("# SubsystemFactory.getAutoSaveSubsystem()");
	if (autosaveInstance == null) {
	    if (LOG.isDebugEnabled())
		LOG.debug("# new instance has to be created");
	    // read fully-qualified class name of implementing class from
	    // ConfigUtility
	    String fullClassName = ConfigUtility.loadProperty(CONFIG_FILE_NAME,
		    CONFIG_KEY_SYSTEM_AUTOSAVE_SUBSYSTEM);
	    if (fullClassName != null) {
		// create new instance with constructor without parameters
		autosaveInstance = (AutoSaveInterface) createInstance(
			fullClassName, null, null);
	    }
	    if (autosaveInstance == null) {
		if (LOG.isWarnEnabled())
		    LOG.warn("# autosave subsystem could not be instantiated");
		throw new FlawedConfigurationException(
			"Config file ["
				+ CONFIG_FILE_NAME
				+ "] or its value of key["
				+ CONFIG_KEY_LOGIN_SUBSYSTEM
				+ "] is faulty. -> AutoSave subsystem could not be instantiated.",
			Subystem.AUTOSAVE);
	    }
	}
	return autosaveInstance;
    }

    /**
     * Gets the only instance existing of {@link SubsystemFactory}.
     * 
     * @return An instance of {@link SubsystemFactory}.
     */
    public static SubsystemFactory getInstance() {
	if (LOG.isTraceEnabled())
	    LOG.trace("# SubsystemFactory.getInstance()");
	if (instance == null)
	    instance = new SubsystemFactory();
	return instance;
    }

    /**
     * Creates an instance of the given class.<br>
     * The constructor with the specified parameter types is chosen and supplied
     * with the given objects.
     * 
     * @param className
     *            The fully-qualified class name of the class which shall be
     *            created.
     * @param classParam
     *            The types of the constructor's parameters.
     * @param objectParam
     *            The objects given to the constructor.
     * @return An instance of the given class or <code>null</code> if an error
     *         occurred.
     */
    private Object createInstance(String className, Class<?>[] classParam,
	    Object[] objectParam) {
	if (LOG.isTraceEnabled())
	    LOG.trace("# SubsystemFactory.createInstance(" + className + ", "
		    + Arrays.toString(classParam) + ", "
		    + Arrays.toString(objectParam) + ")");
	try {
	    Class<?> cl = Class.forName(className);
	    java.lang.reflect.Constructor<?> co = cl.getConstructor(classParam);
	    if (LOG.isInfoEnabled())
		LOG.info("# class[" + className + "] succesfully instantiated");
	    return co.newInstance(objectParam);
	} catch (Exception e) {
	    if (LOG.isErrorEnabled())
		LOG.error(
			"# An error occurred while creating instance of class["
				+ className + "]", e);
	    return null;
	}
    }

}
